package com.javabi.codegenerator.java;

import static java.util.Collections.synchronizedList;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.javabi.codegenerator.java.model.JavaModel;
import com.javabi.codegenerator.java.model.JavaModelBuilder;
import com.javabi.codegenerator.java.model.bean.JavaModelBean;
import com.javabi.codegenerator.java.model.datastore.JavaModelDataStore;
import com.javabi.codegenerator.java.model.eenum.JavaModelEnum;
import com.javabi.codegenerator.java.model.executable.JavaModelExecutableSerializer;
import com.javabi.codegenerator.java.model.executable.JavaModelExecutableSet;
import com.javabi.codegenerator.java.model.iinterface.JavaModelInterface;
import com.javabi.codegenerator.java.model.lookup.IJavaModelSet;
import com.javabi.codegenerator.java.model.lookup.JavaModelSet;
import com.javabi.codegenerator.java.model.serializer.JavaModelDataSerializer;
import com.javabi.codegenerator.java.model.serializer.JavaModelJsonSerializer;
import com.javabi.codegenerator.java.model.serializer.JavaModelXmlSerializer;
import com.javabi.codegenerator.java.model.servlet.JavaModelServletRequestParser;
import com.javabi.codegenerator.java.model.sql.JavaModelResultSetParser;
import com.javabi.codegenerator.java.model.validator.JavaModelValidator;
import com.javabi.codegenerator.java.object.IJavaGenerator;
import com.javabi.codegenerator.java.object.adapter.JavaAdapterGenerator;
import com.javabi.codegenerator.java.object.alias.JavaAliasGenerator;
import com.javabi.codegenerator.java.object.annotation.JavaAnnotationGenerator;
import com.javabi.codegenerator.java.object.bean.JavaBeanGenerator;
import com.javabi.codegenerator.java.object.comparator.JavaComparatorGenerator;
import com.javabi.codegenerator.java.object.datastore.JavaDataStoreGenerator;
import com.javabi.codegenerator.java.object.delegate.JavaDelegateGenerator;
import com.javabi.codegenerator.java.object.eenum.JavaEnumGenerator;
import com.javabi.codegenerator.java.object.executable.JavaExecutableSerializerGenerator;
import com.javabi.codegenerator.java.object.executable.JavaExecutableSetGenerator;
import com.javabi.codegenerator.java.object.factory.JavaObjectFactoryGenerator;
import com.javabi.codegenerator.java.object.id.ModelIdRegistry;
import com.javabi.codegenerator.java.object.iinterface.JavaInterfaceGenerator;
import com.javabi.codegenerator.java.object.model.IJavaContext;
import com.javabi.codegenerator.java.object.model.JavaContext;
import com.javabi.codegenerator.java.object.serializer.generator.data.JavaDataSerializerGenerator;
import com.javabi.codegenerator.java.object.serializer.generator.json.JavaJsonSerializerGenerator;
import com.javabi.codegenerator.java.object.serializer.generator.xml.JavaXmlSerializerGenerator;
import com.javabi.codegenerator.java.object.servlet.JavaServletRequestParserGenerator;
import com.javabi.codegenerator.java.object.sql.JavaResultSetParserGenerator;
import com.javabi.codegenerator.java.object.validator.JavaValidatorGenerator;
import com.javabi.codegenerator.java.object.validator.lookup.IJavaValidatorLookup;
import com.javabi.codegenerator.java.object.validator.lookup.JavaValidatorLookup;
import com.javabi.codegenerator.java.setup.ISetup;
import com.javabi.codegenerator.java.setup.source.ISource;
import com.javabi.codegenerator.java.setup.target.ITarget;
import com.javabi.codegenerator.model.IModel;
import com.javabi.codegenerator.model.object.adaptor.ModelAdapter;
import com.javabi.codegenerator.model.object.alias.ModelAlias;
import com.javabi.codegenerator.model.object.annotation.ModelAnnotation;
import com.javabi.codegenerator.model.object.comparator.ModelComparator;
import com.javabi.codegenerator.model.object.delegate.ModelDelegate;
import com.javabi.codegenerator.model.object.factory.ModelObjectFactory;
import com.javabi.codegenerator.model.reader.IModelReader;
import com.javabi.codegenerator.model.reader.ModelReaderLookup;
import com.javabi.codeobject.java.type.interfacemap.IJavaExtendsMap;
import com.javabi.codeobject.java.type.interfacemap.JavaExtendsMap;
import com.javabi.codeobject.java.type.resolver.IJavaTypeResolver;
import com.javabi.codeobject.java.type.resolver.JavaTypeResolver;

public class JavaModelGenerator {

	private final ExecutorService service;
	private final IJavaTypeResolver resolver;
	private final IJavaExtendsMap extendsMap = new JavaExtendsMap();
	private final IJavaValidatorLookup validatorLookup = new JavaValidatorLookup();
	private final ISetup setup;

	public JavaModelGenerator(ISetup setup, int threads) {
		this(setup, Executors.newFixedThreadPool(threads));
	}

	public JavaModelGenerator(ISetup setup, ExecutorService executor) {
		if (executor == null) {
			throw new NullPointerException("service");
		}
		if (setup == null) {
			throw new NullPointerException("setup");
		}
		this.service = executor;
		this.setup = setup;

		// Prepare the resolver
		resolver = new JavaTypeResolver().registerCommonTypes();
	}

	public void generate() {
		try {

			// Clean directories
			cleanTargetDirectories();

			// Model set
			IJavaModelSet modelSet = readModelSet();

			// Read models and register types
			List<IJavaGenerator> generators = readAndRegisterTypes(modelSet);

			// Secondary types
			registerSecondaryTypes(generators);

			// Verify types
			verifyTypes(generators);

			// Generate!
			generate(generators);

		} catch (RuntimeException re) {
			throw re;
		} catch (Exception e) {
			throw new JavaModelGenerationException(e);
		}
	}

	private IJavaModelSet readModelSet() {
		final ModelIdRegistry registry = new ModelIdRegistry();
		List<IModelReader> readerList = getReaderList();

		final List<JavaModel> modelList = synchronizedList(new ArrayList<JavaModel>());
		LatchedExecutor executor = new LatchedExecutor(service, readerList);
		for (final IModelReader reader : readerList) {
			executor.submit(new Runnable() {

				@Override
				public void run() {
					IModel model = reader.readModel();
					registry.setIds(model);

					JavaModel javaModel = new JavaModelBuilder(model).build();
					modelList.add(javaModel);
				}

			});
		}

		executor.await();
		return new JavaModelSet(modelList);
	}

	private void cleanTargetDirectories() {
		List<ITarget> list = setup.getTargetList();
		LatchedExecutor executor = new LatchedExecutor(service, list);
		for (final ITarget target : list) {
			executor.submit(new Runnable() {

				@Override
				public void run() {
					target.getWriter().clean();
				}
			});
		}
		executor.await();
	}

	private void generate(List<IJavaGenerator> generators) {
		// Asynchronous
		LatchedExecutor executor = new LatchedExecutor(service, generators);
		for (final IJavaGenerator generator : generators) {
			executor.submit(new Runnable() {

				@Override
				public void run() {
					generator.generate();
				}
			});
		}
		executor.await();
	}

	private void registerSecondaryTypes(List<IJavaGenerator> generators) {
		// Asynchronous
		LatchedExecutor executor = new LatchedExecutor(service, generators);
		for (final IJavaGenerator generator : generators) {
			executor.submit(new Runnable() {

				@Override
				public void run() {
					generator.registerSecondaryTypes();
				}
			});
		}
		executor.await();
	}

	private void verifyTypes(List<IJavaGenerator> generators) {
		// Asynchronous
		LatchedExecutor executor = new LatchedExecutor(service, generators);
		for (final IJavaGenerator generator : generators) {
			executor.submit(new Runnable() {

				@Override
				public void run() {
					generator.verifyReferencedTypes();
				}
			});
		}
		executor.await();
	}

	private List<IJavaGenerator> readAndRegisterTypes(final IJavaModelSet modelSet) {

		// Thread safe list please!
		final List<IJavaGenerator> generators = synchronizedList(new ArrayList<IJavaGenerator>());

		// Asynchronous
		List<JavaModel> modelList = modelSet.getModelList();
		LatchedExecutor executor = new LatchedExecutor(service, modelList);
		for (final JavaModel model : modelList) {
			executor.submit(new Runnable() {

				@Override
				public void run() {
					List<IJavaGenerator> list = readAndRegisterTypes(modelSet, model);
					generators.addAll(list);
				}

			});
		}
		executor.await();
		return generators;
	}

	private List<IModelReader> getReaderList() {
		ModelReaderLookup lookup = new ModelReaderLookup();
		for (ISource source : setup.getSourceList()) {
			lookup.add(source.getName());
		}
		return lookup.getReaderList();
	}

	private List<IJavaGenerator> readAndRegisterTypes(IJavaModelSet modelSet, JavaModel javaModel) {
		List<IJavaGenerator> generators = new ArrayList<IJavaGenerator>();

		IModel model = javaModel.get();
		IJavaContext context = new JavaContext(modelSet, resolver, extendsMap, validatorLookup);

		// Aliases
		for (ModelAlias alias : model.getAliasList()) {
			IJavaGenerator generator = new JavaAliasGenerator(setup, context, javaModel, alias);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		// Validator
		for (JavaModelValidator validator : javaModel.getValidatorList()) {
			IJavaGenerator generator = new JavaValidatorGenerator(setup, context, javaModel, validator);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		// Enums
		for (JavaModelEnum eenum : javaModel.getEnumList()) {
			IJavaGenerator generator = new JavaEnumGenerator(setup, context, javaModel, eenum);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		// Beans
		for (JavaModelBean bean : javaModel.getBeanList()) {
			IJavaGenerator generator = new JavaBeanGenerator(setup, context, javaModel, bean);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		// Comparators
		for (ModelComparator comparator : model.getComparatorList()) {
			IJavaGenerator generator = new JavaComparatorGenerator(setup, context, javaModel, comparator);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		// Adapters
		for (ModelAdapter adapter : model.getAdapterList()) {
			IJavaGenerator generator = new JavaAdapterGenerator(setup, context, javaModel, adapter);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		// Interfaces
		for (JavaModelInterface iinterface : javaModel.getInterfaceList()) {
			IJavaGenerator generator = new JavaInterfaceGenerator(setup, context, javaModel, iinterface);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		// Factory
		for (ModelObjectFactory factory : model.getObjectFactoryList()) {
			IJavaGenerator generator = new JavaObjectFactoryGenerator(setup, context, javaModel, factory);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		// Data Stores
		for (JavaModelDataStore dataStore : javaModel.getDataStoreList()) {
			IJavaGenerator generator = new JavaDataStoreGenerator(setup, context, javaModel, dataStore);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		// Delegates
		for (ModelDelegate delegate : model.getDelegateList()) {
			IJavaGenerator generator = new JavaDelegateGenerator(setup, context, javaModel, delegate);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		// ResultSet Parsers
		for (JavaModelResultSetParser parser : javaModel.getResultSetParserList()) {
			IJavaGenerator generator = new JavaResultSetParserGenerator(setup, context, javaModel, parser);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		// Servlet Request Parsers
		for (JavaModelServletRequestParser parser : javaModel.getServletRequestParserList()) {
			IJavaGenerator generator = new JavaServletRequestParserGenerator(setup, context, javaModel, parser);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		// Data Serializer
		for (JavaModelDataSerializer serializer : javaModel.getDataSerializerList()) {
			IJavaGenerator generator = new JavaDataSerializerGenerator(setup, context, javaModel, serializer);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		// XML Serializer
		for (JavaModelXmlSerializer serializer : javaModel.getXmlSerializerList()) {
			IJavaGenerator generator = new JavaXmlSerializerGenerator(setup, context, javaModel, serializer);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		// JSON Serializer
		for (JavaModelJsonSerializer serializer : javaModel.getJsonSerializerList()) {
			IJavaGenerator generator = new JavaJsonSerializerGenerator(setup, context, javaModel, serializer);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		// Annotations
		for (ModelAnnotation adapter : model.getAnnotationList()) {
			IJavaGenerator generator = new JavaAnnotationGenerator(setup, context, javaModel, adapter);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		// Executable Set
		for (JavaModelExecutableSet set : javaModel.getExecutableSetList()) {
			IJavaGenerator generator = new JavaExecutableSetGenerator(setup, context, javaModel, set);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		// Executable Serializer
		for (JavaModelExecutableSerializer serializer : javaModel.getExecutableSerializerList()) {
			IJavaGenerator generator = new JavaExecutableSerializerGenerator(setup, context, javaModel, serializer);
			generator.registerPrimaryTypes();
			generators.add(generator);
		}

		return generators;
	}

}
